package titt.service.proxy;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.objenesis.ObjenesisStd;

import titt.service.invoke.AdvancedMethodInvoker;
import titt.type.Type;
import titt.type.TypedObject;

public class TITTProxyGenerator {

	private final Map<Class<?>, Class<?>> cache = new HashMap<Class<?>, Class<?>>();
	private class ProxyInvoker implements MethodInterceptor {
		private final AdvancedMethodInvoker ti;
		
		public ProxyInvoker(AdvancedMethodInvoker ti) {
			this.ti = ti;
		}
		public Object intercept(Object proxy, Method m, Object[] args, MethodProxy mp) throws Throwable {			
			try {
				if(m.getName().equals("finalize")) {
					return null;
				}
				TypedObject[] targs = new TypedObject[args.length];
				Class<?>[] cls = m.getParameterTypes();
				
				for(int i = 0; i < targs.length; i++)
					targs[i] = new TypedObject(new Type(cls[i]), args[i]);
				
				synchronized (proxy) {
					return ti.call(new Type(m.getReturnType()), m.getName(), targs).getObject();					
				}

			}
			catch(Exception e) {
				if(e instanceof RuntimeException) 
					throw (RuntimeException)e;
				throw new RuntimeException(e);
			}
		}		
	}
	
	private Class<?> createSubClass(Class<?> c) {
		if(cache.containsKey(c)) return cache.get(c);
		
		Enhancer e = new Enhancer();

		if(c.isInterface())
			e.setInterfaces(new Class[]{c});
		else
			e.setSuperclass(c);

		e.setCallbackType(ProxyInvoker.class);
		Class<?> sc = e.createClass();

		cache.put(c, sc);

		return sc;
	}
	
	public Object createTITTProxy(final Class<?> c, AdvancedMethodInvoker ami) {
		Factory f = (Factory)new ObjenesisStd().newInstance(createSubClass(c));

		f.setCallback(0, new ProxyInvoker(ami));
		
		return f;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T createGenericTITTProxy(Class<T> c, AdvancedMethodInvoker ami) throws Exception {
		return (T)createTITTProxy(c, ami);
	}

}
